﻿using System.Text;
using System.Text.RegularExpressions;

namespace System
{
    /// <summary>
    /// .NET types extensions.
    /// </summary>
    public static class Extensions
    {
        private static readonly Regex EmailRegex =
            new Regex("[.\\-_a-z0-9]+@([a-z0-9][\\-a-z0-9]+\\.)+[a-z]{2,6}", Options);

        private static readonly DateTime UnixTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        private const long JsonUnixTicksMultiplexer = 1000*1000*10;

        private const RegexOptions Options =
            RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled;

        private static readonly char[] Lookup =
            new[]
                {
                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
                };

        /// <summary>
        /// Converts UNIX(JSON) ticks into equivalent .NET <see cref="DateTime">DateTime</see> value.
        /// </summary>
        /// <param name="ticks">UNIX(JSON) Ticks.</param>
        /// <returns>Equivalent <see cref="DateTime">DateTime</see> value.</returns>
        public static DateTime JsonUnixTicksToDateTime(this double ticks)
        {
            return new DateTime(UnixTime.Ticks + (long) (ticks*JsonUnixTicksMultiplexer),
                                DateTimeKind.Utc);
        }

        /// <summary>
        /// Determines whether input is e-mail or not.
        /// </summary>
        /// <param name="str">Input value.</param>
        /// <returns>True if e-mail, otherwise false.</returns>
        public static bool IsEmail(this string str)
        {
            var m = EmailRegex.Match(str);

            return m.Success;
        }

        /// <summary>
        /// Converts byte-array to Hexadecimal string presentation.
        /// </summary>
        /// <param name="data">Byte-array.</param>
        /// <returns>Hexadecimal string presentation.</returns>
        public static string ToHexString(this byte[] data)
        {
            if (data == null)
            {
                return string.Empty;
            }

            var i = 0;
            var p = 0;
            var l = data.Length;

            var c = new char[l * 2];

            while (i < l)
            {
                var d = data[i++];
                c[p++] = Lookup[d / 0x10];
                c[p++] = Lookup[d % 0x10];
            }

            return new string(c, 0, c.Length);
        }

        /// <summary>
        /// Converts string to byte-array.
        /// </summary>
        /// <param name="str">Source string.</param>
        /// <returns>Resulted byte-array.</returns>
        public static byte[] ToByteArray(this string str)
        {
            return ToByteArray(str, 0, 0, 0);
        }

        private static byte[] ToByteArray(this string str, int offset, int step, int tail)
        {
            var b = new byte[(str.Length - offset - tail + step) / (2 + step)];
            var l = str.Length - tail;
            var s = step + 1;

            for (int y = 0, x = offset; x < l; ++y, x += s)
            {
                var c1 = (byte)str[x];
                var c2 = (byte)str[++x];

                if (c1 > 0x60)
                {
                    c1 -= 0x57;
                }
                else if (c1 > 0x40)
                {
                    c1 -= 0x37;
                }
                else
                {
                    c1 -= 0x30;
                }

                if (c2 > 0x60)
                {
                    c2 -= 0x57;
                }
                else if (c2 > 0x40)
                {
                    c2 -= 0x37;
                }
                else
                {
                    c2 -= 0x30;
                }

                b[y] = (byte)((c1 << 4) + c2);
            }

            return b;
        }

        /// <summary>
        /// Converts <see cref="int">value</see> to Roman presentation.
        /// </summary>
        /// <param name="number"><see cref="int">Value</see>to be converted.</param>
        /// <param name="nullValue">Replacement to be returned in case value is 0.</param>
        /// <returns>Roman presentation.</returns>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static string ToRomanString(this int number, string nullValue = null)
        {
            if (-9999 >= number || number >= 9999)
            {
                throw new ArgumentOutOfRangeException("number");
            }

            if (number == 0)
            {
                return nullValue;
            }

            var sb = new StringBuilder(10);

            if (number < 0)
            {
                sb.Append('-');
                number *= -1;
            }

            var table = new[,]
                            {
                                {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"},
                                {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"},
                                {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"},
                                {"", "M", "MM", "MMM", "M(V)", "(V)", "(V)M", "(V)MM", "(V)MMM", "M(X)"}
                            };

            for (int i = 1000, j = 3; i > 0; i /= 10, j--)
            {
                var digit = number / i;
                sb.Append(table[j, digit]);
                number -= digit * i;
            }

            return sb.ToString();
        }

        /// <summary>
        /// Fires <see cref="EventHandler{TEventArgs}">event</see> with prior check whether it has subscribers or not.
        /// </summary>
        /// <typeparam name="TEventArgs">Custom <see cref="EventArgs">event arguments</see> type.</typeparam>
        /// <param name="handler">Generic <see cref="EventHandler{TEventArgs}">event handler</see>.</param>
        /// <param name="target">Target object.</param>
        /// <param name="e">Custom <see cref="EventArgs">event arguments</see>.</param>
        public static void Fire<TEventArgs>(this EventHandler<TEventArgs> handler, object target, TEventArgs e)
            where TEventArgs : EventArgs
        {
            if (null != handler)
            {
                handler(target, e);
            }
        }

        /// <summary>
        /// Fires <see cref="EventHandler">event</see> with prior check whether it has subscribers or not.
        /// </summary>
        /// <param name="handler"><see cref="EventHandler">Event handler</see>.</param>
        /// <param name="target">Target object.</param>
        /// <param name="e"><see cref="EventArgs">Event arguments</see>.</param>
        public static void Fire(this EventHandler handler, object target, EventArgs e)
        {
            if (null != handler)
            {
                handler(target, e);
            }
        }
    }
}
